#!/usr/bin/env python3

import argparse
import os
import re
import sys

from natsort import natsorted
from tabulate import tabulate
from utilities_common import constants
from utilities_common import multi_asic as multi_asic_util
from utilities_common.intf_filter import parse_interface_in_filter
from sonic_py_common.interface import get_intf_longname

# mock the redis for unit test purposes #
try:
    if os.environ["UTILITIES_UNIT_TESTING"] == "2":
        modules_path = os.path.join(os.path.dirname(__file__), "..")
        tests_path = os.path.join(modules_path, "tests")
        sys.path.insert(0, modules_path)
        sys.path.insert(0, tests_path)
        import mock_tables.dbconnector
    if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic":
        import mock_tables.mock_multi_asic
        mock_tables.dbconnector.load_namespace_config()

except KeyError:
    pass

# ========================== Common interface-utils logic ==========================


PORT_STATUS_TABLE_PREFIX = "PORT_TABLE:"
PORT_STATE_TABLE_PREFIX = "PORT_TABLE|"
PORT_TRANSCEIVER_TABLE_PREFIX = "TRANSCEIVER_INFO|"
PORT_LANES_STATUS = "lanes"
PORT_ALIAS = "alias"
PORT_OPER_STATUS = "oper_status"
PORT_ADMIN_STATUS = "admin_status"
PORT_SPEED = "speed"
PORT_MTU_STATUS = "mtu"
PORT_FEC = "fec"
PORT_DESCRIPTION = "description"
PORT_OPTICS_TYPE = "type"
PORT_PFC_ASYM_STATUS = "pfc_asym"
PORT_AUTONEG = 'autoneg'
PORT_ADV_SPEEDS = 'adv_speeds'
PORT_RMT_ADV_SPEEDS = 'rmt_adv_speeds'
PORT_INTERFACE_TYPE = 'interface_type'
PORT_ADV_INTERFACE_TYPES = 'adv_interface_types'
PORT_TPID = "tpid"
OPTICS_TYPE_RJ45 = 'RJ45'
PORT_LINK_TRAINING = 'link_training'
PORT_LINK_TRAINING_STATUS = 'link_training_status'

VLAN_SUB_INTERFACE_SEPARATOR = "."
VLAN_SUB_INTERFACE_TYPE = "802.1q-encapsulation"

SUB_PORT = "subport"

def get_frontpanel_port_list(config_db):
    ports_dict = config_db.get_table('PORT')
    front_panel_ports_list = []
    for port in ports_dict:
        front_panel_ports_list.append(port)
    return front_panel_ports_list


def get_sub_port_intf_list(config_db):
    sub_intf_dict = config_db.get_table('VLAN_SUB_INTERFACE')
    sub_intf_list = []
    for sub_intf in sub_intf_dict:
        if isinstance(sub_intf, str):
            sub_intf_list.append(sub_intf)
    return sub_intf_list


def get_interface_vlan_dict(config_db):
    """
    Get info from REDIS ConfigDB and create interface to vlan mapping
    """
    get_int_vlan_configdb_info = config_db.get_table('VLAN_MEMBER')
    int_list = []
    vlan_list = []
    for line in get_int_vlan_configdb_info:
        vlan_number = line[0]
        interface = line[1]
        int_list.append(interface)
        vlan_list.append(vlan_number)
    int_to_vlan_dict = dict(zip(int_list, vlan_list))
    return int_to_vlan_dict


def config_db_vlan_port_keys_get(int_to_vlan_dict, front_panel_ports_list, intf_name):
    """
    Get interface vlan value and return it.
    """
    vlan = "routed"
    if intf_name in front_panel_ports_list:
        if intf_name in int_to_vlan_dict.keys():
            vlan = int_to_vlan_dict[intf_name]
            if "Vlan" in vlan:
                vlan = "trunk"
    return vlan


def appl_db_keys_get(appl_db, front_panel_ports_list, intf_name):
    """
    Get APPL_DB Keys
    """
    if intf_name is None:
        appl_db_keys = appl_db.keys(appl_db.APPL_DB, "PORT_TABLE:*")
    elif intf_name in front_panel_ports_list:
        appl_db_keys = appl_db.keys(appl_db.APPL_DB, "PORT_TABLE:%s" % intf_name)
    else:
        return None
    return appl_db_keys


def appl_db_sub_intf_keys_get(appl_db, sub_intf_list, sub_intf_name):
    """
    Get APPL_DB sub port interface keys
    """
    if sub_intf_name is None:
        appl_db_sub_intf_keys = []
        appl_db_intf_keys = appl_db.keys(appl_db.APPL_DB, "INTF_TABLE:*")
        if appl_db_intf_keys is not None:
            for appl_db_intf_key in appl_db_intf_keys:
                if re.split(':', appl_db_intf_key, maxsplit=1)[-1].strip() in sub_intf_list:
                    appl_db_sub_intf_keys.append(appl_db_intf_key)
    elif sub_intf_name in sub_intf_list:
        appl_db_sub_intf_keys = appl_db.keys(appl_db.APPL_DB, "INTF_TABLE:%s" % sub_intf_name)
    else:
        return []
    return appl_db_sub_intf_keys


def port_speed_parse(in_speed, optics_type):
    """
    Parse the speed received from DB
    """
    # fetched speed is in megabits per second
    speed = int(in_speed)
    if optics_type == OPTICS_TYPE_RJ45 and speed <= 1000:
        out_speed = '{}M'.format(speed)
    elif speed < 1000:
        out_speed = '{}M'.format(speed)
    elif speed % 1000 >= 100:
        out_speed = '{:.1f}G'.format(speed / 1000)
    else:
        out_speed = '{:.0f}G'.format(speed / 1000)

    return out_speed

def appl_db_port_status_get(appl_db, intf_name, status_type):
    """
    Get the port status
    """
    full_table_id = PORT_STATUS_TABLE_PREFIX + intf_name
    status = appl_db.get(appl_db.APPL_DB, full_table_id, status_type)
    if status is None:
        return "N/A"
    if status_type == PORT_SPEED and status != "N/A":
        optics_type = state_db_port_optics_get(appl_db, intf_name, PORT_OPTICS_TYPE)
        status = port_speed_parse(status, optics_type)
    elif status_type == PORT_ADV_SPEEDS and status != "N/A" and status != "all":
        optics_type = state_db_port_optics_get(appl_db, intf_name, PORT_OPTICS_TYPE)
        speed_list = status.split(',')
        new_speed_list = []
        for s in natsorted(speed_list):
            new_speed_list.append(port_speed_parse(s, optics_type))
        status = ','.join(new_speed_list)
    return status

def state_db_port_status_get(db, intf_name, field):
    """
    Get the port status
    """
    full_table_id = PORT_STATE_TABLE_PREFIX + intf_name
    status = db.get(db.STATE_DB, full_table_id, field)
    if not status:
        return "N/A"
    if field in [PORT_RMT_ADV_SPEEDS] and status not in ["N/A", "all"]:
        optics_type = state_db_port_optics_get(db, intf_name, PORT_OPTICS_TYPE)
        speed_list = status.split(',')
        new_speed_list = []
        for s in natsorted(speed_list):
            new_speed_list.append(port_speed_parse(s, optics_type))
        status = ','.join(new_speed_list)
    return status

def port_oper_speed_get(db, intf_name):
    """
    Get port oper speed
    """
    oper_speed = db.get(db.STATE_DB, PORT_STATE_TABLE_PREFIX + intf_name, PORT_SPEED)
    oper_status = db.get(db.APPL_DB, PORT_STATUS_TABLE_PREFIX + intf_name, PORT_OPER_STATUS)
    if oper_speed is None or oper_speed == "N/A" or oper_status != "up":
        return appl_db_port_status_get(db, intf_name, PORT_SPEED)
    else:
        optics_type = state_db_port_optics_get(db, intf_name, PORT_OPTICS_TYPE)
        return port_speed_parse(oper_speed, optics_type)

def port_oper_speed_get_raw(db, intf_name):
    """
    Get port raw speed. E.g. 100000, 50000 and so on.
    """
    speed = db.get(db.STATE_DB, PORT_STATE_TABLE_PREFIX + intf_name, PORT_SPEED)
    oper_status = db.get(db.APPL_DB, PORT_STATUS_TABLE_PREFIX + intf_name, PORT_OPER_STATUS)
    if speed is None or speed == "N/A" or oper_status != "up":
        speed = db.get(db.APPL_DB, PORT_STATUS_TABLE_PREFIX + intf_name, PORT_SPEED)
    return speed

def state_db_port_optics_get(state_db, intf_name, type):
    """
    Get optic type info for port
    """
    full_table_id = PORT_TRANSCEIVER_TABLE_PREFIX + intf_name
    optics_type = state_db.get(state_db.STATE_DB, full_table_id, type)
    if optics_type is None:
        return "N/A"
    return optics_type

def merge_dicts(x,y):
    # store a copy of x, but overwrite with y's values where applicable
    merged = dict(x,**y)
    xkeys = x.keys()
    # if the value of merged[key] was overwritten with y[key]'s value
    # then we need to put back any missing x[key] values
    for key in xkeys:
        # if this key is a dictionary, recurse
        if isinstance(x[key], dict) and key in y:
            merged[key] = merge(x[key],y[key])
    return merged

def tuple_to_dict(tup, new_dict):
    """
    From a tuple create a dictionary that uses the first item in the tuple as a key
    and the 2nd item in the tuple as a value.
    """
    for a, b in tup:
        new_dict.setdefault(a, []).append(b)
    return new_dict


def get_raw_portchannel_info(config_db):
    """
    This function uses the redis config_db as input and gets the "PORTCHANNEL_MEMBER" table
    create
    >>> get_po_int_configdb_info = get_portchannel_info(config_db)
    >>> pprint(get_po_int_configdb_info)
    {('PortChannel0001', 'Ethernet108'): {},
     ('PortChannel0001', 'Ethernet112'): {},
     ('PortChannel0002', 'Ethernet116'): {},
     ('PortChannel0003', 'Ethernet120'): {},
     ('PortChannel0004', 'Ethernet124'): {}}
    This function returns a dictionary with the key being portchannels and interface tuple.
    """
    get_raw_po_int_configdb_info = config_db.get_table('PORTCHANNEL_MEMBER')
    return get_raw_po_int_configdb_info     # Return a dictionary with the key being the portchannel and interface

def get_portchannel_list(get_raw_po_int_configdb_info):
    """
    >>> portchannel_list = get_portchannel_list(get_raw_po_int_configdb_info)
    >>> pprint(portchannel_list)
    ['PortChannel0001', 'PortChannel0002', 'PortChannel0003', 'PortChannel0004']
    >>>
    """
    portchannel_list = []
    for po in get_raw_po_int_configdb_info:
        portchannel = po[0]
        if portchannel not in portchannel_list:
            portchannel_list.append(portchannel)
    return natsorted(portchannel_list)

def create_po_int_tuple_list(get_raw_po_int_configdb_info):
    """
    >>> po_int_tuple = get_raw_po_int_configdb_info.keys()
    >>> pprint(po_int_tuple_list)
    [('PortChannel0001', 'Ethernet108'),
     ('PortChannel0002', 'Ethernet116'),
     ('PortChannel0004', 'Ethernet124'),
     ('PortChannel0003', 'Ethernet120'),
     ('PortChannel0001', 'Ethernet112')]
    >>>
    """
    po_int_tuple_list = get_raw_po_int_configdb_info.keys()
    return po_int_tuple_list

def create_po_int_dict(po_int_tuple_list):
    """
    This function takes the portchannel to interface tuple
    and converts that into a portchannel to interface dictionary
    with the portchannels as the key and the interfaces as the values.
    """
    temp_dict = {}
    po_int_dict = tuple_to_dict(po_int_tuple_list, temp_dict)
    return po_int_dict

def create_int_to_portchannel_dict(po_int_tuple_list):
    """
    This function takes the portchannel to interface tuple
    and converts that into an interface to portchannel dictionary
    with the interfaces as the key and the portchannels as the values.
    """
    int_po_dict = {}
    for po, intf in po_int_tuple_list:
        int_po_dict.setdefault(intf, po)
    return int_po_dict

def po_speed_dict(po_int_dict, appl_db):
    """
    This function takes the portchannel to interface dictionary
    and the appl_db and then creates a portchannel to speed
    dictionary.
    """
    if po_int_dict:
        po_list = []
        for key, value in po_int_dict.items():
            agg_speed_list =  []
            po_list.append(key)
            if len(value) == 1:
                interface_speed = port_oper_speed_get_raw(appl_db, value[0])
                if interface_speed is None:
                    # If no speed was returned, append None without format
                    po_list.append(None)
                else:
                    optics_type = state_db_port_optics_get(appl_db, value[0], PORT_OPTICS_TYPE)
                    interface_speed = port_speed_parse(interface_speed, optics_type)
                    po_list.append(interface_speed)
            elif len(value) > 1:
                for intf in value:
                    temp_speed = port_oper_speed_get_raw(appl_db, intf)
                    optics_type = state_db_port_optics_get(appl_db, intf, PORT_OPTICS_TYPE)
                    temp_speed = int(temp_speed) if temp_speed else 0
                    agg_speed_list.append(temp_speed)
                    interface_speed = sum(agg_speed_list)
                    interface_speed = str(interface_speed)
                    interface_speed = port_speed_parse(interface_speed, optics_type)
                po_list.append(interface_speed)
            po_speed_dict = dict(po_list[i:i+2] for i in range(0, len(po_list), 2))
        return po_speed_dict
    else:
        po_speed_dict = {}
        return po_speed_dict

def appl_db_portchannel_status_get(appl_db, config_db, po_name, status_type, portchannel_speed_dict, combined_int_to_vlan_po_dict=None):
    """
    Get the port status
    """
    full_table_id = "LAG_TABLE:" + po_name
    po_table_id = "PORTCHANNEL|" + po_name
    #print(full_table_id)
    if status_type == "speed":
        status = portchannel_speed_dict[po_name]
        if status is None:
            return "N/A"
        return status
    if status_type == "vlan":
        if combined_int_to_vlan_po_dict and po_name in combined_int_to_vlan_po_dict.keys():
            status = "trunk"
        else:
            status = "routed"
        return status
    if status_type == "mtu":
        status = config_db.get(config_db.CONFIG_DB, po_table_id, status_type)
        return status
    if status_type == "tpid":
        status = config_db.get(config_db.CONFIG_DB, po_table_id, status_type)
        if status is None:
            return "0x8100"
        return status
    status = appl_db.get(appl_db.APPL_DB, full_table_id, status_type)
    #print(status)
    if status is None:
        return "N/A"
    return status

def appl_db_sub_intf_status_get(appl_db, config_db, front_panel_ports_list, portchannel_speed_dict, sub_intf_name, status_type):
    sub_intf_sep_idx = sub_intf_name.find(VLAN_SUB_INTERFACE_SEPARATOR)
    if sub_intf_sep_idx != -1:
        parent_port_name = get_intf_longname(sub_intf_name[:sub_intf_sep_idx])

        full_intf_table_name = "INTF_TABLE" + ":" + sub_intf_name

        if status_type == "vlan":
            vlan_id = appl_db.get(appl_db.APPL_DB, full_intf_table_name, status_type)
            return vlan_id

        if status_type == "admin_status":
            status = appl_db.get(appl_db.APPL_DB, full_intf_table_name, status_type)
            return status if status is not None else "N/A"

        if status_type == "type":
            return VLAN_SUB_INTERFACE_TYPE

        if status_type == "mtu" or status_type == "speed":
            if parent_port_name in front_panel_ports_list:
                return appl_db_port_status_get(appl_db, parent_port_name, status_type)
            elif parent_port_name in portchannel_speed_dict.keys():
                return appl_db_portchannel_status_get(appl_db, config_db, parent_port_name, status_type, portchannel_speed_dict)
            else:
                return "N/A"

    return "N/A"

# ========================== interface-status logic ==========================

header_stat = ['Interface', 'Lanes', 'Speed', 'MTU', 'FEC', 'Alias', 'Vlan', 'Oper', 'Admin', 'Type', 'Asym PFC']
header_stat_sub_intf = ['Sub port interface', 'Speed', 'MTU', 'Vlan', 'Admin', 'Type']


class IntfStatus(object):

    def __init__(self, intf_name, namespace_option, display_option):
        """
        Class constructor method
        :param self:
        :param intf_name: string of interface
        :return:
        """
        self.db = None
        self.config_db = None
        self.sub_intf_only = False
        self.intf_name = intf_name
        self.sub_intf_name = intf_name
        self.table = []
        self.multi_asic = multi_asic_util.MultiAsic(
            display_option, namespace_option)
        if intf_name is not None:
            if intf_name == SUB_PORT:
                self.intf_name = None
                self.sub_intf_name = None
                self.sub_intf_only = True
            else:
                sub_intf_sep_idx = intf_name.find(VLAN_SUB_INTERFACE_SEPARATOR)
                if sub_intf_sep_idx != -1:
                    self.sub_intf_only = True
                    self.intf_name = intf_name[:sub_intf_sep_idx]

    def display_intf_status(self):
        self.get_intf_status()
        sorted_table = natsorted(self.table)
        print(tabulate(sorted_table,
                       header_stat if not self.sub_intf_only else header_stat_sub_intf,
                       tablefmt="simple",
                       stralign='right'))

    def generate_intf_status(self):
        """
            Generate interface-status output
        """

        i = {}
        table = []
        key = []

        intf_fs = parse_interface_in_filter(self.intf_name)
        #
        # Iterate through all the keys and append port's associated state to
        # the result table.
        #
        if not self.sub_intf_only:
            for i in self.appl_db_keys:
                key = re.split(':', i, maxsplit=1)[-1].strip()
                if key in self.front_panel_ports_list:
                    if self.multi_asic.skip_display(constants.PORT_OBJ, key):
                        continue

                    if self.intf_name is None or key in intf_fs:
                        table.append((key,
                                appl_db_port_status_get(self.db, key, PORT_LANES_STATUS),
                                port_oper_speed_get(self.db, key),
                                appl_db_port_status_get(self.db, key, PORT_MTU_STATUS),
                                appl_db_port_status_get(self.db, key, PORT_FEC),
                                appl_db_port_status_get(self.db, key, PORT_ALIAS),
                                config_db_vlan_port_keys_get(self.combined_int_to_vlan_po_dict, self.front_panel_ports_list, key),
                                appl_db_port_status_get(self.db, key, PORT_OPER_STATUS),
                                appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS),
                                state_db_port_optics_get(self.db, key, PORT_OPTICS_TYPE),
                                appl_db_port_status_get(self.db, key, PORT_PFC_ASYM_STATUS)))

            for po, value in self.portchannel_speed_dict.items():
                if po:
                    if self.multi_asic.skip_display(constants.PORT_CHANNEL_OBJ, po):
                        continue
                    if self.intf_name is None or po in intf_fs:
                        table.append((po,
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_LANES_STATUS, self.portchannel_speed_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_SPEED, self.portchannel_speed_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_MTU_STATUS, self.portchannel_speed_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_FEC, self.portchannel_speed_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_ALIAS, self.portchannel_speed_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, "vlan", self.portchannel_speed_dict, self.combined_int_to_vlan_po_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_OPER_STATUS, self.portchannel_speed_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_ADMIN_STATUS, self.portchannel_speed_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_OPTICS_TYPE, self.portchannel_speed_dict),
                                appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_PFC_ASYM_STATUS, self.portchannel_speed_dict)))
        else:
            for key in self.appl_db_sub_intf_keys:
                sub_intf = re.split(':', key, maxsplit=1)[-1].strip()
                if sub_intf in self.sub_intf_list:
                    table.append((sub_intf,
                                appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_SPEED),
                                appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_MTU_STATUS),
                                appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, "vlan"),
                                appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_ADMIN_STATUS),
                                appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_OPTICS_TYPE)))
        return table


    @multi_asic_util.run_on_multi_asic
    def get_intf_status(self):
        self.front_panel_ports_list = get_frontpanel_port_list(self.config_db)
        self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, None)
        self.int_to_vlan_dict = get_interface_vlan_dict(self.config_db)
        self.get_raw_po_int_configdb_info = get_raw_portchannel_info(self.config_db)
        self.portchannel_list = get_portchannel_list(self.get_raw_po_int_configdb_info)
        self.po_int_tuple_list = create_po_int_tuple_list(self.get_raw_po_int_configdb_info)
        self.po_int_dict = create_po_int_dict(self.po_int_tuple_list)
        self.int_po_dict = create_int_to_portchannel_dict(self.po_int_tuple_list)
        self.combined_int_to_vlan_po_dict = merge_dicts(self.int_to_vlan_dict, self.int_po_dict)
        self.portchannel_speed_dict = po_speed_dict(self.po_int_dict, self.db)
        self.portchannel_keys = self.portchannel_speed_dict.keys()

        self.sub_intf_list = get_sub_port_intf_list(self.config_db)
        self.appl_db_sub_intf_keys = appl_db_sub_intf_keys_get(self.db, self.sub_intf_list, self.sub_intf_name)
        if self.appl_db_keys:
            self.table += self.generate_intf_status()

# ========================== interface-description logic ==========================


header_desc = ['Interface', 'Oper', 'Admin', 'Alias', 'Description']


class IntfDescription(object):

    def __init__(self, intf_name, namespace_option, display_option):
        self.db = None
        self.config_db = None
        self.table = []
        self.multi_asic = multi_asic_util.MultiAsic(
            display_option, namespace_option)

        if intf_name is not None and intf_name == SUB_PORT:
            self.intf_name = None
        else:
            self.intf_name = intf_name

    def display_intf_description(self):

        self.get_intf_description()

        # Sorting and tabulating the result table.
        sorted_table = natsorted(self.table)
        print(tabulate(sorted_table, header_desc, tablefmt="simple", stralign='right'))

    def generate_intf_description(self):
        """
            Generate interface-description output
        """

        i = {}
        table = []
        key = []

        #
        # Iterate through all the keys and append port's associated state to
        # the result table.
        #
        for i in self.appl_db_keys:
            key = re.split(':', i, maxsplit=1)[-1].strip()
            if key in self.front_panel_ports_list:
                if self.multi_asic.skip_display(constants.PORT_OBJ, key):
                        continue
                table.append((key,
                              appl_db_port_status_get(self.db, key, PORT_OPER_STATUS),
                              appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS),
                              appl_db_port_status_get(self.db, key, PORT_ALIAS),
                              appl_db_port_status_get(self.db, key, PORT_DESCRIPTION)))
        return table

    @multi_asic_util.run_on_multi_asic
    def get_intf_description(self):
        self.front_panel_ports_list = get_frontpanel_port_list(self.config_db)
        self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, self.intf_name)
        if self.appl_db_keys:
            self.table += self.generate_intf_description()


# ========================== interface-autoneg logic ==========================
header_autoneg = ['Interface', 'Auto-Neg Mode', 'Speed', 'Adv Speeds', 'Rmt Adv Speeds', 'Type', 'Adv Types', 'Oper', 'Admin']


class IntfAutoNegStatus(object):

    def __init__(self, intf_name, namespace_option, display_option):
        self.db = None
        self.config_db = None
        self.table = []
        self.multi_asic = multi_asic_util.MultiAsic(
            display_option, namespace_option)

        if intf_name is not None and intf_name == SUB_PORT:
            self.intf_name = None
        else:
            self.intf_name = intf_name

    def display_autoneg_status(self):

        self.get_intf_autoneg_status()

        # Sorting and tabulating the result table.
        sorted_table = natsorted(self.table)
        print(tabulate(sorted_table, header_autoneg, tablefmt="simple", stralign='right'))

    def generate_autoneg_status(self):
        """
            Generate interface-autoneg output
        """

        i = {}
        table = []
        key = []

        #
        # Iterate through all the keys and append port's associated state to
        # the result table.
        #
        for i in self.appl_db_keys:
            key = re.split(':', i, maxsplit=1)[-1].strip()
            if key in self.front_panel_ports_list:
                if self.multi_asic.skip_display(constants.PORT_OBJ, key):
                    continue
                autoneg_mode = appl_db_port_status_get(self.db, key, PORT_AUTONEG)
                if autoneg_mode != 'N/A':
                    autoneg_mode = 'enabled' if autoneg_mode == 'on' else 'disabled'
                table.append((key,
                              autoneg_mode,
                              port_oper_speed_get(self.db, key),
                              appl_db_port_status_get(self.db, key, PORT_ADV_SPEEDS),
                              state_db_port_status_get(self.db, key, PORT_RMT_ADV_SPEEDS),
                              appl_db_port_status_get(self.db, key, PORT_INTERFACE_TYPE),
                              appl_db_port_status_get(self.db, key, PORT_ADV_INTERFACE_TYPES),
                              appl_db_port_status_get(self.db, key, PORT_OPER_STATUS),
                              appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS),
                              ))
        return table

    @multi_asic_util.run_on_multi_asic
    def get_intf_autoneg_status(self):
        self.front_panel_ports_list = get_frontpanel_port_list(self.config_db)
        self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, self.intf_name)
        if self.appl_db_keys:
            self.table += self.generate_autoneg_status()


# ========================== interface-tpid logic ==========================

header_tpid = ['Interface', 'Alias', 'Oper', 'Admin', 'TPID']

class IntfTpid(object):

    def __init__(self, intf_name, namespace_option, display_option):
        """
        Class constructor method
        :param self:
        :param intf_name: string of interface
        :return:
        """
        self.db = None
        self.config_db = None
        self.intf_name = intf_name
        self.table = []
        self.multi_asic = multi_asic_util.MultiAsic(
            display_option, namespace_option)

        if intf_name is not None and intf_name == SUB_PORT:
            self.intf_name = None

    def display_intf_tpid(self):
        self.get_intf_tpid()

        # Sorting and tabulating the result table.
        sorted_table = natsorted(self.table)
        print(tabulate(sorted_table, header_tpid, tablefmt="simple", stralign='right'))

    def generate_intf_tpid(self):
        """
            Generate interface-tpid output
        """

        i = {}
        table = []
        key = []

        intf_fs = parse_interface_in_filter(self.intf_name)
        #
        # Iterate through all the keys and append port's associated state to
        # the result table.
        #
        for i in self.appl_db_keys:
            key = re.split(':', i, maxsplit=1)[-1].strip()
            if key in self.front_panel_ports_list:
                if self.multi_asic.skip_display(constants.PORT_OBJ, key):
                    continue

                if self.intf_name is None or key in intf_fs:
                    table.append((key,
                        appl_db_port_status_get(self.db, key, PORT_ALIAS),
                        appl_db_port_status_get(self.db, key, PORT_OPER_STATUS),
                        appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS),
                        appl_db_port_status_get(self.db, key, PORT_TPID)))

        for po, value in self.po_speed_dict.items():
            if po:
                if self.multi_asic.skip_display(constants.PORT_CHANNEL_OBJ, po):
                    continue
                if self.intf_name is None or po in intf_fs:
                    table.append((po,
                        appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_ALIAS, self.po_speed_dict),
                        appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_OPER_STATUS, self.po_speed_dict),
                        appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_ADMIN_STATUS, self.po_speed_dict),
                        appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_TPID, self.po_speed_dict)))
        return table

    @multi_asic_util.run_on_multi_asic
    def get_intf_tpid(self):
        self.front_panel_ports_list = get_frontpanel_port_list(self.config_db)
        self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, None)
        self.get_raw_po_int_configdb_info = get_raw_portchannel_info(self.config_db)
        self.portchannel_list = get_portchannel_list(self.get_raw_po_int_configdb_info)
        self.po_int_tuple_list = create_po_int_tuple_list(self.get_raw_po_int_configdb_info)
        self.po_int_dict = create_po_int_dict(self.po_int_tuple_list)
        self.int_po_dict = create_int_to_portchannel_dict(self.po_int_tuple_list)
        self.po_speed_dict = po_speed_dict(self.po_int_dict, self.db)
        self.portchannel_keys = self.po_speed_dict.keys()

        if self.appl_db_keys:
            self.table += self.generate_intf_tpid()


# ========================== interface-link-training logic ==========================
header_link_training = ['Interface', 'LT Oper', 'LT Admin', 'Oper', 'Admin']

class IntfLinkTrainingStatus(object):

    def __init__(self, intf_name, namespace_option, display_option):
        self.db = None
        self.config_db = None
        self.table = []
        self.multi_asic = multi_asic_util.MultiAsic(
            display_option, namespace_option)

        if intf_name is not None and intf_name == SUB_PORT:
            self.intf_name = None
        else:
            self.intf_name = intf_name

    def display_link_training_status(self):
        self.get_intf_link_training_status()
        # Sorting and tabulating the result table.
        sorted_table = natsorted(self.table)
        print(tabulate(sorted_table, header_link_training, tablefmt="simple", stralign='right'))

    @multi_asic_util.run_on_multi_asic
    def get_intf_link_training_status(self):
        self.front_panel_ports_list = get_frontpanel_port_list(self.config_db)
        self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, self.intf_name)
        if self.appl_db_keys:
            self.table += self.generate_link_training_status()

    def generate_link_training_status(self):
        """
            Generate interface-link-training output
        """

        i = {}
        table = []
        key = []

        #
        # Iterate through all the keys and append port's associated state to
        # the result table.
        #
        for i in self.appl_db_keys:
            key = re.split(':', i, maxsplit=1)[-1].strip()
            if key in self.front_panel_ports_list:
                if self.multi_asic.skip_display(constants.PORT_OBJ, key):
                    continue
                lt_admin = appl_db_port_status_get(self.db, key, PORT_LINK_TRAINING)
                if lt_admin not in ['on', 'off']:
                    lt_admin = '-'
                lt_status = state_db_port_status_get(self.db, key, PORT_LINK_TRAINING_STATUS)
                if lt_status in ['N/A', '', None]:
                    lt_status = 'off'
                table.append((key,
                              lt_status.replace('_', ' '),
                              lt_admin,
                              appl_db_port_status_get(self.db, key, PORT_OPER_STATUS),
                              appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS)))
        return table

def main():
    parser = argparse.ArgumentParser(description='Display Interface information',
                                     formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('-c', '--command', type=str, help='get interface status or description or auto negotiation status or tpid', default=None)
    parser.add_argument('-i', '--interface', type=str, help='interface information for specific port: Ethernet0', default=None)
    parser = multi_asic_util.multi_asic_args(parser)
    args = parser.parse_args()

    if args.command == "status":
        interface_stat = IntfStatus(args.interface, args.namespace, args.display)
        interface_stat.display_intf_status()
    elif args.command == "description":
        interface_desc = IntfDescription(args.interface, args.namespace, args.display)
        interface_desc.display_intf_description()
    elif args.command == "autoneg":
        interface_autoneg_status = IntfAutoNegStatus(args.interface, args.namespace, args.display)
        interface_autoneg_status.display_autoneg_status()
    elif args.command == "tpid":
        interface_tpid = IntfTpid(args.interface, args.namespace, args.display)
        interface_tpid.display_intf_tpid()
    elif args.command == "link_training":
        interface_lt_status = IntfLinkTrainingStatus(args.interface, args.namespace, args.display)
        interface_lt_status.display_link_training_status()

    sys.exit(0)

if __name__ == "__main__":
     main()
